// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedParameter.Local

/*
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Drawing;

using AM.Reporting.Data;
using AM.Reporting.Utils;

#endregion

#nullable enable

namespace AM.Reporting.Table
{
    /// <summary>
    /// Represents a table column.
    /// </summary>
    /// <remarks>
    /// Use the <see cref="Width"/> property to set the width of a column. If <see cref="AutoSize"/>
    /// property is <b>true</b>, the column will calculate its width automatically.
    /// <para/>You can also set the <see cref="MinWidth"/> and <see cref="MaxWidth"/> properties
    /// to restrict the column's width.
    /// </remarks>
    public partial class TableColumn : ComponentBase
    {
        #region Fields

        private float saveWidth;
        private bool saveVisible;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets a width of the column, in pixels.
        /// </summary>
        /// <remarks>
        /// The column width cannot exceed the range defined by the <see cref="MinWidth"/>
        /// and <see cref="MaxWidth"/> properties.
        /// <note>To convert between pixels and report units, use the constants defined
        /// in the <see cref="Units"/> class.</note>
        /// </remarks>
        [TypeConverter ("AM.Reporting.TypeConverters.UnitsConverter, AM.Reporting")]
        public override float Width
        {
            get => base.Width;
            set
            {
                value = Converter.DecreasePrecision (value, 2);
                if (value > MaxWidth)
                {
                    value = MaxWidth;
                }

                if (value < MinWidth)
                {
                    value = MinWidth;
                }

                if (FloatDiff (base.Width, value))
                {
                    UpdateLayout (value - base.Width);
                    base.Width = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the minimal width for this column, in pixels.
        /// </summary>
        [DefaultValue (0f)]
        [Category ("Layout")]
        [TypeConverter ("AM.Reporting.TypeConverters.UnitsConverter, AM.Reporting")]
        public float MinWidth { get; set; }

        /// <summary>
        /// Gets or sets the maximal width for this column, in pixels.
        /// </summary>
        [DefaultValue (5000f)]
        [Category ("Layout")]
        [TypeConverter ("AM.Reporting.TypeConverters.UnitsConverter, AM.Reporting")]
        public float MaxWidth { get; set; }

        /// <summary>
        /// Gets or sets a value determines if the column should calculate its width automatically.
        /// </summary>
        /// <remarks>
        /// The column width cannot exceed the range defined by the <see cref="MinWidth"/>
        /// and <see cref="MaxWidth"/> properties.
        /// </remarks>
        [DefaultValue (false)]
        [Category ("Behavior")]
        public bool AutoSize { get; set; }

        /// <summary>
        /// Gets the index of this column.
        /// </summary>
        [Browsable (false)]
        public int Index { get; private set; }

        /// <inheritdoc/>
        [Browsable (false)]
        public override float Left
        {
            get
            {
                if (Parent is not TableBase table)
                {
                    return 0;
                }

                float result = 0;
                for (var i = 0; i < Index; i++)
                {
                    result += table.Columns[i].Width;
                }

                return result;
            }
            set => base.Left = value;
        }

        /// <summary>
        /// Gets or sets the page break flag for this column.
        /// </summary>
        [Browsable (false)]
        public bool PageBreak { get; set; }

        /// <summary>
        /// Gets or sets the number of columns to keep on the same page.
        /// </summary>
        [Browsable (false)]
        public int KeepColumns { get; set; }

        internal float MinimumBreakWidth { get; set; }

        internal static float DefaultWidth => (int)Math.Round (64 / (0.25f * Units.Centimeters)) * (0.25f * Units.Centimeters);

        #endregion

        #region Private Methods

        private void UpdateLayout (float dx)
        {
            if (Parent is not TableBase table)
            {
                return;
            }

            // update this column cells
            foreach (TableRow row in table.Rows)
            {
                row.CellData (Index).UpdateLayout (dx, 0);
            }

            // update spanned cells that contains this column
            var spanList = table.GetSpanList();
            foreach (var span in spanList)
            {
                if (Index > span.Left && Index < span.Right)
                {
                    table[span.Left, span.Top].CellData.UpdateLayout (dx, 0);
                }
            }
        }

        #endregion

        #region Public Methods

        /// <inheritdoc/>
        public override void Assign (Base source)
        {
            var src = source as TableColumn;
            MinWidth = src.MinWidth;
            MaxWidth = src.MaxWidth;
            AutoSize = src.AutoSize;
            KeepColumns = src.KeepColumns;

            base.Assign (source);
        }

        /// <inheritdoc/>
        public override void Serialize (ReportWriter writer)
        {
            var c = writer.DiffObject as TableColumn;
            base.Serialize (writer);

            if (FloatDiff (MinWidth, c.MinWidth))
            {
                writer.WriteFloat ("MinWidth", MinWidth);
            }

            if (FloatDiff (MaxWidth, c.MaxWidth))
            {
                writer.WriteFloat ("MaxWidth", MaxWidth);
            }

            if (FloatDiff (Width, c.Width))
            {
                writer.WriteFloat ("Width", Width);
            }

            if (AutoSize != c.AutoSize)
            {
                writer.WriteBool ("AutoSize", AutoSize);
            }
        }

        public void SetIndex (int value)
        {
            Index = value;
        }

        internal void SaveState()
        {
            saveWidth = Width;
            saveVisible = Visible;
        }

        internal void RestoreState()
        {
            Width = saveWidth;
            Visible = saveVisible;
        }

        /// <inheritdoc/>
        public override void Clear()
        {
            if (Parent is not TableBase grid)
            {
                return;
            }

            var colIndex = grid.Columns.IndexOf (this);
            foreach (TableRow row in grid.Rows)
            {
                row[colIndex].Dispose();
            }

            base.Clear();
        }

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="TableColumn"/> class.
        /// </summary>
        public TableColumn()
        {
            MaxWidth = 5000;
            Width = DefaultWidth;
            SetFlags (Flags.CanCopy | Flags.CanDelete | Flags.CanWriteBounds, false);
            BaseName = "Column";
        }
    }
}
